import Lexer from '../lexer'

import {
  POSIX,
  BinaryProperties,
  Control,
  GeneralCategory,
  Scripts,
  patternOf
} from '../ast/regex-aliases'

import sign from '../ast/sign'

const
  alternation = 'Alternation',
  anchor      = 'Anchor',
  any         = 'Any',
  binary      = 'BinaryPropertie',
  codepoint   = 'Codepoint',
  general     = 'GeneralCategory',
  group       = 'Group',
  matcher     = 'Matcher',
  native      = 'Native',
  negative    = 'Negative',
  posix       = 'POSIX',
  quantifier  = 'Quantifier',
  range       = 'Range',
  reference   = 'Reference',
  script      = 'Script',
  series      = 'Series',
  set         = 'Set'

const
  A =  ncp('a'),
  B =  ncp('b'),
  BAR = ncp(0x7C)

// global.log = (...cp)=> console.log(cp.map(cp=>cp.toString(16)))

function ncp (cp) {
  if (typeof cp === 'string')
    cp = cp.codePointAt(0)
  return node(codepoint, cp)
}

function test (src, body) {
  let
    lexer = new Lexer('\\' + src + '/', ''),
    regex = lexer.fragment()

  expect(lexer.error).to.be.equal('')
  expect(regex.sign).to.be.equal(sign.regex)

  if (body.length === 0)
    expect(regex.body.length).to.equal(0)
  else
    deep(regex.body, body)
}

function testAliases (sign, a) {
  for (let i = 0; i < a.length; i++) {
    let  abbr = a[i++], full = a[i]
    test(`\\p{${abbr}}\\P{${abbr}}`, [
      node(sign, abbr),
      node(negative, node(sign, abbr)),
    ])
    test(`\\p{${full}}\\P{${full}}`, [
      node(sign, abbr),
      node(negative, node(sign, abbr)),
    ])
  }
}

function deep (fact, want) {
  fact.forEach((p, i) => {
    let g = want[i], sign = patternOf(p.sign)
    expect(!!g).to.equal(true, 'unexpected nodes')
    if (g.sign == null) {
      expect(sign).to.equal(g)
      return
    }
    expect(sign).to.equal(g.sign)
    if (g.data != null) {
      if (typeof g.data !== 'object')
        expect(p.data).to.equal(g.data)
      else {
        expect(p.data).to.be.an('object')
        deep([p.data], [g.data])
      }
    } if (g.body) {
      expect(p.body).to.be.an('array')
      expect(p.body.length).to.equal(g.body.length)
      deep(p.body, g.body)
    }
  })
  expect(fact.length).to.equal(want.length)
}

function node (sign, data, body) {
  return body
    && {sign, data, body}
    || {sign, data}
}

describe('regexp', () => {
  it('elements',  () => {
    test('', [])
    test('^$\\B\\E\\b\\e', [
      node(anchor, '^'),
      node(anchor, '$'),
      node(anchor, 'B'),
      node(anchor, 'E'),
      node(anchor, 'b'),
      node(anchor, 'e'),
    ])
    test('a𝌐\\f\\n\\r\\t\\v\\/.', [
      A,
      ncp('𝌐'),
      ncp('\f'),
      ncp('\n'),
      ncp('\r'),
      ncp('\t'),
      ncp('\v'),
      ncp('/'),
      any,
    ])
    test(' |', [
      ncp(0x20),
      BAR,
    ])
    test('a|', [
      A,
      BAR,
    ])
    test('a|b|', [
      node(alternation, 0, [
        A,
        node(series, 0, [
          B,
          BAR,
        ])
      ])
    ])
    test('[- a-][^a-b]', [
      node(set, 0, [
        ncp(0x2D),
        ncp(0x20),
        A,
        ncp(0x2D),
      ]),
      node(negative, node(set, 0, [
        node(range, 0, [A, B]),
      ])),
    ])
    test('[({[.})/\\]]', [
      node(set, 0, [
        ncp('('),
        ncp('{'),
        ncp('['),
        ncp('.'),
        ncp('}'),
        ncp(')'),
        ncp('/'),
        ncp(']'),
      ]),
    ])
    test('[[:alpha:]][^[:alpha:]][[:^alpha:]]', [
      node(set, 0, [
        node(posix, 'alpha')
      ]),
      node(negative, node(set, 0, [
        node(posix, 'alpha')
      ])),
      node(set, 0, [
        node(negative, node(posix, 'alpha'))
      ]),
    ])
    test('(|)(a|)(a|b)', [
      node(group, '', [
        BAR
      ]),
      node(group, '', [
        A, BAR
      ]),
      node(group, '', [
        node(alternation, 0, [A, B])
      ]),
    ])
    test('(|(a|(a|b)))', [
      node(group, '', [
        BAR,
        node(group, '', [
          node(alternation, 0, [
            A,
            node(group, '', [
              node(alternation, 0, [A, B])
            ]),
          ])
        ]),
      ]),
    ])
    test('(!a)(&a)(:a)(<name>a<>)', [
      node(group, '!', [A]),
      node(group, '&', [A]),
      node(group, ':', [A]),
      node(group, 'name', [A, ncp('<'), ncp('>')])
    ])
    test('(?-)a(?-:a)(?i-m)(?i-)', [
      node(group, '?-', []),
      A,
      node(group, '?-', [A]),
      node(group, '?i-m', []),
      node(group, '?i-', [])
    ])
    test('\\o{4177777}\\u{10FFFF}\\x78\\O{170}\\U{78}\\X78', [
      ncp(0x10FFFF),
      ncp(0x10FFFF),
      ncp(0x78),
      node(negative, ncp(0x78)),
      node(negative, ncp(0x78)),
      node(negative, ncp(0x78)),
    ])
    test('^$\\B\\E\\b\\e', [
      node(anchor, '^'),
      node(anchor, '$'),
      node(anchor, 'B'),
      node(anchor, 'E'),
      node(anchor, 'b'),
      node(anchor, 'e'),
    ])
    test('\\aa\\da\\ha\\la\\sa\\wa', [
      node(native, 'a'), A,
      node(native, 'd'), A,
      node(native, 'h'), A,
      node(native, 'l'), A,
      node(native, 's'), A,
      node(native, 'w'), A,
    ])
    test('\\Aa\\Da\\Ha\\La\\Sa\\Wa', [
      node(negative, node(native, 'a')), A,
      node(negative, node(native, 'd')), A,
      node(negative, node(native, 'h')), A,
      node(negative, node(native, 'l')), A,
      node(negative, node(native, 's')), A,
      node(negative, node(native, 'w')), A,
    ])
    test('\\0a\\1a\\2a\\3a\\4a\\5a\\6a\\7a\\8a\\9a', [
      node(matcher, 0), A,
      node(matcher, 1), A,
      node(matcher, 2), A,
      node(matcher, 3), A,
      node(matcher, 4), A,
      node(matcher, 5), A,
      node(matcher, 6), A,
      node(matcher, 7), A,
      node(matcher, 8), A,
      node(matcher, 9), A,
    ])
    test('\\1\\2a', [
      node(matcher, 12), A
    ])
    test('*?', [
      ncp('*'),
      node(quantifier, '?')
    ])
    test('a*a*?a+a+?a?a??', [
      A, node(quantifier, '*'),
      A, node(quantifier, '*?'),
      A, node(quantifier, '+'),
      A, node(quantifier, '+?'),
      A, node(quantifier, '?'),
      A, node(quantifier, '??'),
    ])
    test('a{1}a{1,}a{1,2}a{1}?', [
      A, node(quantifier, '=', [
        node(quantifier, 1)
      ]),
      A, node(quantifier, '=', [
        node(quantifier, 1),
        node(quantifier, -1)
      ]),
      A, node(quantifier, '=', [
        node(quantifier, 1),
        node(quantifier, 2)
      ]),
      A, node(quantifier, '=?', [
        node(quantifier, 1)
      ]),
    ])
    test('\\g<na-1-me-0>', [
      node(reference, 'na-1-me-0')
    ])
  })

  it('Control',  () => {
    for (let i = 0; i < Control.length; i++) {
      let abbr = Control[i++], val = Control[i]

      if (abbr.length === 2) abbr = '-' + abbr

      test(`\\c${abbr}\\C${abbr}`, [
        ncp(val),
        node(negative, ncp(val)),
      ])
    }
  })

  it('POSIX',  () => {
    for (let i = 0; i < POSIX.length; i++) {
      let name = POSIX[i]
      test(`[:${name}:][:^${name}:]`, [
        node(posix, name),
        node(negative, node(posix, name)),
      ])
    }
  })

  it('BinaryProperties',  () => {
    testAliases(binary, BinaryProperties)
  })

  it('GeneralCategory',  () => {
    testAliases(general, GeneralCategory)
  })

  it('Scripts',  () => {
    testAliases(script, Scripts)
  })
})
